/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief MaCICtrl interface Server implementation.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * $Id: MaCICtrlServer.cpp,v 1.24 2009-05-13 07:18:17 amaula Exp $
 *
 */

#include "MaCICtrlServer.hpp"

//*****************************************************************************
#define nobreak ;
#define GIMI_CLIENT_API (20000)
#include "gimi.h"
//*****************************************************************************
//*****************************************************************************
using namespace MaCI::MaCICtrl;
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CMaCICtrlServer::CMaCICtrlServer(gimi::GIMI *aGIMIPtr, const int aInterfaceMinor)
  : CSync(1,1),
    CThread(1),
    iServerActive(false),
    iInformationMessage(),
    iInformationMessageDirty(true),
    iMaCIGroupName(""),
    iMaCIGroupID(0),
    iServiceArray(),
    iServiceIdentificationMap(),
    iServiceAuthentication(),
    iGIMIPtr(aGIMIPtr),
    iInterfaceMajor(GIMI_PROTOCOL_MACI_MACICTRL),
    iInterfaceMinor(aInterfaceMinor),
    iRedirectionBufferId(-1)
{
  // Create internal BinBag for InformationMessage (because only sending out)
  iInformationMessage.CreateInternalBinBag();

  // This requests a new redirectionID if GIMI is present.
  iRedirectionBufferId = iGIMIPtr->reserveBufferIdentifier();
  dPrint(8,"Got '%d' for RedirectionBufferId from GIMI(%p)",
         iRedirectionBufferId, iGIMIPtr);
}
//*****************************************************************************

CMaCICtrlServer::~CMaCICtrlServer()
{
  Close();

}
//*****************************************************************************

MaCI::EMaCIError CMaCICtrlServer::Open(void)
{
  MaCI::EMaCIError result = KMaCIError;
  assert(this != NULL);
  assert(iGIMIPtr != NULL);

  // Execute only if server is not active.
  if (!iServerActive) {
    // Register service.
    //RegisterProvidedService("MaCI - MaCICtrl Interface");
    bool r;


    // If InterfaceMinor is < 0, attempt to autogenerate it.
    if (iInterfaceMinor < 0) {
      // Start from Zero.
      iInterfaceMinor = 0;

      // Find a 'Free' TypeID pair.
      while( iGIMIPtr->isFreeTypeId(iInterfaceMajor, iInterfaceMinor) == false)
        ++iInterfaceMinor;
    }

    // Add Accepted service
    r = iGIMIPtr->addAcceptedService(iInterfaceMajor,
                                     iInterfaceMinor,
                                     "MaCI - MaCICtrlServer Interface");

    // Add Redirection
    r &= iGIMIPtr->addRedirection(iRedirectionBufferId,
                                  iInterfaceMajor,
                                  iInterfaceMinor);
    if (r) {
      dPrint(6,"Succesfully registered acceptedService '%d:%d'",
             iInterfaceMajor, iInterfaceMinor);

    } else {
      dPrint(6,"AcceptedService %d:%d already exists.",
             iInterfaceMajor, iInterfaceMinor);

    }

    iServerActive = true;
    RunThread();

    result = KMaCIOK;
    dPrint(6,"Succesfully Open():ed MaCICtrlServer instance %p",
           this);

  } else {
    dPrint(8,"Server already active, will not start again.");

  }
  return result;
}
//*****************************************************************************

MaCI::EMaCIError CMaCICtrlServer::Close(void)
{
  MaCI::EMaCIError result = KMaCIError;
  assert(this != NULL);
  assert(iGIMIPtr != NULL);

  // Execute block only if server is currently active.
  if (iServerActive) {
    iServerActive = false;
    
    // Unregister service
    bool r;

    // Remove accepted service
    r = iGIMIPtr->removeAcceptedService(iInterfaceMajor,
                                        iInterfaceMinor);

    // Remove redirection
    r &= iGIMIPtr->removeRedirection(iRedirectionBufferId,
                                     iInterfaceMajor,
                                     iInterfaceMinor);
    if (r) {
      dPrint(6,"Succesfully unregistered acceptedService '%d:%d'",
             iInterfaceMajor, iInterfaceMinor);

    } else {
      dPrint(6,"AcceptedService '%d:%d' doesn't exist (Hence not unregistered)",
             iInterfaceMajor, iInterfaceMinor);

    }

    // This releases the boundBufferIdentifier
    if (iRedirectionBufferId != 0) {
      dPrint(ODTEST,"Releasing RedirectionBufferId '%d'.", iRedirectionBufferId);
      iGIMIPtr->releaseBufferIdentifier(iRedirectionBufferId);
    }

    // Stop the handler Thread
    WaitThread();
    result = KMaCIOK;

    dPrint(4,"Succesfully Close():ed MaCICtrlServer instance %p", this);
  }

  return result;
}
//*****************************************************************************

bool CMaCICtrlServer::SetGroupName(const std::string &aName)
{
  bool result = false;

  if (aName.size() > 0) {
    dPrint(8,"MaCI groupname set to '%s'", aName.c_str());
    iMaCIGroupName = aName;
    if (!iInformationMessageDirty) iInformationMessageDirty = true;
    result = true;
  }

  return result;
}
//*****************************************************************************

std::string CMaCICtrlServer::GetGroupName(void) const
{
  return iMaCIGroupName;
}
//*****************************************************************************

bool CMaCICtrlServer::AddGroupService(const unsigned int aMajor,
                                      const unsigned int aMinor,
                                      const EServiceType aType,
                                      const std::string &aInterfaceInstanceName)
{
  bool result = false;
  TServiceArrayIterator sai = FindService(aMajor, aMinor, aType);
  if (sai == iServiceArray.end()) {
    const TService s(aMajor, aMinor, aType);
    iServiceArray.push_back(s);

    // If name is set, push it too.
    if (aInterfaceInstanceName.size() > 0) {
      TServiceIdentification si(aInterfaceInstanceName);
      std::pair<TService, TServiceIdentification> p(s, si);
      iServiceIdentificationMap.insert(p);
    }

    if (!iInformationMessageDirty) iInformationMessageDirty = true;
    result = true;
  }
  return result;
}
//*****************************************************************************

bool CMaCICtrlServer::RemoveGroupService(const unsigned int aMajor,
                                         const unsigned int aMinor,
                                         const EServiceType aType)
{
  bool result = false;
  TServiceArrayIterator sai = FindService(aMajor, aMinor, aType);
  if (sai != iServiceArray.end()) {
    iServiceArray.erase(sai);
    if (!iInformationMessageDirty) iInformationMessageDirty = true;
    result = true;
  }
  return result;
}
//*****************************************************************************

void CMaCICtrlServer::ClearGroupServices(void)
{
  if (!iInformationMessageDirty) iInformationMessageDirty = true;
  iServiceArray.clear();
}
//*****************************************************************************

const TServiceArray &CMaCICtrlServer::GetServiceArray(void) const
{
  return iServiceArray;
}
//*****************************************************************************

void CMaCICtrlServer::SetAccessKey(const unsigned int aMajor,
                                   const unsigned int aMinor,
                                   const unsigned char *aKey,
                                   const unsigned int aKeyLen,
                                   const int aMaxAccess,
                                   const int aAccessTimeout_ms)
{
  // Construct keys.
  const TServiceAuthenticationData sad(TAuthentication(aKey, aKeyLen), aMaxAccess, aAccessTimeout_ms);
  const TServiceAuthenticationKey sak(aMajor, aMinor);

  dPrint(6,"Constructed new AuthenticationData for service %u:%u, maxAccess: %d, accessTimeout: %dms",
         aMajor, aMinor, aMaxAccess, aAccessTimeout_ms);

  // Get iterator for Service Authentication map.
  TServiceAuthenticationMap::iterator sam_it = iServiceAuthentication.find(sak);

  if (sam_it != iServiceAuthentication.end()) {
    // Replace existing. (Access map is not copied, because key change invalidates
    // all accesses anyway)
    sam_it->second = sad;
    dPrint(6,"Replaced existing access key for service %u:%u",
           aMajor, aMinor);

  } else {
    // Insert new entry in Map.
    iServiceAuthentication.insert(TServiceAuthenticationDataPair(sak, sad));
    dPrint(6,"Inserted new access key for service %u:%u",
           aMajor, aMinor);

  }
}
//*****************************************************************************

void CMaCICtrlServer::GetAccessInfo(TAccess &aAccess,
                                    const unsigned int aMajor,
                                    const unsigned int aMinor,
                                    const unsigned int aNodeID,
                                    const bool aUpdateAccessTime)
{
  // Values for default states.
  const TAccess deny(0); // denied
  const TAccess allow(KAccessNormal); // Allowed

  // Get iterator for Service Authentication map.
  TServiceAuthenticationMap::iterator sam_it =
    iServiceAuthentication.find(TServiceAuthenticationKey(aMajor, aMinor));

  // Did we get the ServiceAuthentication entry?
  if (sam_it != iServiceAuthentication.end()) {
    TServiceAuthenticationData &sad = sam_it->second;

    // Now, get a iterator to existing Client entry.
    TClientAccessMap::iterator camit = sad.GetClientAccessEntry(aNodeID);
    if (camit != sad.clientAccessMap.end()) {
      TClientAccessData &cad = camit->second;
      bool access_ok = true;
      // Entry found.

      // Update accesstime if requested.
      if (aUpdateAccessTime) {
        access_ok = UpdateAccess(camit, sad);
      }

      // Check the access_ok value after update (if it was called)
      if (access_ok) {
        // Access is still valid, use the stored version.
        dPrint(6,"Access still valid for NodeID '%u' for service %u:%u, returning stored entry.",
               aNodeID, aMajor, aMinor);
        aAccess = cad.access;

      } else {
        // Access not valid anymore. Give DENIED version.
        dPrint(6,"Access aged out for NodeID '%u' on service %u:%u. Access denied!",
               aNodeID, aMajor, aMinor);
        aAccess = deny;

      }
    } else {
      // No entry found!
      dPrint(6,"No Access entry for NodeID '%u' on service %u:%u. Access denied!",
             aNodeID, aMajor, aMinor);
      aAccess = deny;
    }
  } else {
    // Got neither one. No access required, will return 'accepted'.
    dPrint(6,"No ServiceAuthentication entry found, returning 'allow'");
    aAccess = allow;
  }
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void CMaCICtrlServer::CheckServiceAccessTimeouts(TServiceAuthenticationData &aService)
{
  // ONLY check if accessTimeout is set
  if (aService.accesstimeout >= 0) {

    const gim::time tnow(true); // Current time
    const gim::time tout(aService.accesstimeout); // Timeout value

    // typeof(x.begin()) i = x.begin(); i != x.end(); ++i
    TClientAccessMap::iterator i = aService.clientAccessMap.begin();
    TClientAccessMap::iterator di;

    while(i != aService.clientAccessMap.end()) {
      const TClientAccessData &d = i->second; // Take data reference
      di = i; // Take 'deletable' iterator.
      ++i; // Increment the current.

      // Check for timeout
      if (tnow - d.lastaccesstime > tout) {
        // TIMED OUT! Erase with the deletable iterator (means; is used
        // only for this purpose)
        aService.clientAccessMap.erase(di);
        dPrint(6,"Deleted timeouted (%dms) Access entry.",
               aService.accesstimeout);
      }
    }
  }

  return;
}
//*****************************************************************************

bool CMaCICtrlServer::UpdateAccess(TClientAccessMap::iterator &accessIterator,
                                   TServiceAuthenticationData &aService)
{
  bool result = false;

  // ONLY check if accessTimeout is set
  if (aService.accesstimeout >= 0) {

    const gim::time tnow(true); // Current time
    const gim::time tout(aService.accesstimeout); // Timeout value

    // Take easier reference
    TClientAccessData &cad = accessIterator->second;

    // Check for timeout.
    if (tnow - cad.lastaccesstime > tout) {
      // Timed out! Delete entry.
      aService.clientAccessMap.erase(accessIterator);

    } else {
      // Still valid. Update timestamp.
      cad.lastaccesstime = tnow;
      result = true;

    }
  } else {
    result = true;
    dPrint(6,"No timeout set - not doing Update");

  }

  // Return 'true' when access still valid, 'false' when not.
  return result;
}
  //*****************************************************************************

TServiceArrayIterator CMaCICtrlServer::FindService(const unsigned int aMajor,
                                                   const unsigned int aMinor,
                                                   const EServiceType aType)
{
  for(EACH_IN_i(iServiceArray)) {
    if (i->servicemajor == aMajor &&
        i->serviceminor == aMinor &&
        (i->type == KServiceTypeUnknown ||
         aType == KServiceTypeUnknown ||
         aType == i->type)) return i;
  }
  return iServiceArray.end();
}
//*****************************************************************************

void CMaCICtrlServer::CommandGetAccess(CMaCICtrlData &aReply,
                                       const CMaCICtrlData &aInputData,
                                       const gimi::GIMIMessage &aMsg)
{
  TAccess access;
  const TService *s = aInputData.GetService(0);

  if (s) {
    GetAccessInfo(access,
                  s->servicemajor,
                  s->serviceminor,
                  aMsg.getSenderId(),
                  true);

    bool r;
    r = aReply.SetTimestamp(Common::TTimestamp()); // Set to present time
    assert(r == true);
    r = aReply.SetCommand(TCommand(KCommandTellAccess)); // Set TellAccess
    assert(r == true);
    r = aReply.SetAccess(access); // Set access data.
    assert(r == true);
  }
}
//*****************************************************************************

void CMaCICtrlServer::CommandGetInformation(CMaCICtrlData &aReply,
                                            const CMaCICtrlData &aInputData,
                                            const gimi::GIMIMessage &aMsg)
{
  bool r;

  // At this point, aReply must be clear.
  // Add 'TellInformation' command
  r = aReply.SetCommand(KCommandTellInformation);
  assert(r == true);

  // Add GroupID
  r = aReply.SetDeviceGroup(iMaCIGroupName);
  assert(r == true);

  // Add all registered services.
  for(EACH_IN_i(iServiceArray)) {
    const ITERATOR(iServiceIdentificationMap) simi = iServiceIdentificationMap.find(*i);
    if (simi != iServiceIdentificationMap.end()) {
      // Got identification too, so send it.
      r = aReply.AddService(*i, simi->second);

    } else {
      // No Identification set, so send service only.
      r = aReply.AddService(*i);

    }
    assert(r == true);
  }
}
//*****************************************************************************

bool CMaCICtrlServer::SetClientAuthentication(const unsigned int aMajor,
                                              const unsigned int aMinor,
                                              const unsigned int aNodeID,
                                              const TAuthentication &aAuth)
{
  bool result = false;

  // First of all: Check whether the requested Major/Minor pair can be
  // processed at all:
  if (FindService(aMajor, aMinor, KServiceTypeUnknown) == iServiceArray.end()) {
    // No service found, can not process access information request
    dPrint(6,"Warning: SetClientAuthentication() called for service %u:%u which is not registered!",
           aMajor, aMinor);

  } else {
    // Debug
    dPrint(6,"Found service %u:%u from table of services. Attempting to authenticate nodeID %08x",
           aMajor, aMinor, aNodeID);

    // Get iterator for Service Authentication map.
    TServiceAuthenticationMap::iterator sam_it =
      iServiceAuthentication.find(TServiceAuthenticationKey(aMajor, aMinor));

    // Did we get entry?
    if (sam_it != iServiceAuthentication.end()) {
      TServiceAuthenticationData &sad = sam_it->second;
      // Got ServiceAuthentication (Authentication required)

      // Remove the existing entry unconditionally (Will add it anyway)
      sad.RemoveClientAccessEntry(aNodeID);

      // 'operator ==' checks the key only.
      if (sad.authentication == aAuth) {
        // Key OK.
        dPrint(8,"Key check OK for service %u:%u by nodeID %08x",
               aMajor, aMinor, aNodeID);

        // Check whether the access limiting is in use.
        if (sad.maxaccesscount >= 0) {
          // Convert to unsigned local variable.
          const unsigned int maxac = (unsigned int)sad.maxaccesscount;

          // Internal check to verify that there are no extra clients
          assert(sad.GetAccessCount() <= maxac);

          // Check whether we have free slots, if not, check for timeouted ones.
          if (sad.GetAccessCount() == maxac) {
            // No slots available! (Do Timeout checking on all entries)
            dPrint(8,"No free slots available for service %u:%u, checking timed out entries.",
                   aMajor, aMinor);
            CheckServiceAccessTimeouts(sad);
          }

          // Check whether we have free slots now?
          if (sad.GetAccessCount() < maxac) {
            // Got free slot! So add new entry!
            sad.SetClientAccessEntry(aNodeID, TClientAccessData( TAccess( KAccessNormal ) ));
            result = true;
            dPrint(6,"Authentication accepted on service %u:%u for nodeID %08x!",
                   aMajor, aMinor, aNodeID, sad.GetAccessCount());

          } else {
            // Still no free slots - So the authentication request is denied.
            dPrint(6,"Authentication rejected on service %u:%u for nodeID %08x - No free slots!",
                   aMajor, aMinor, aNodeID);
          }

        } else {
          // No access count limit, accept directly.
          sad.SetClientAccessEntry(aNodeID, TClientAccessData( TAccess( KAccessNormal ) ));
          result = true;
          dPrint(6,"Authentication accepted on service %u:%u for nodeID %08x! Now got %u (max: unlimited) authenticated nodes.",
                 aMajor, aMinor, aNodeID, sad.GetAccessCount());

        }
      } else {
        // Verification failed.
        dPrint(6,"Authentication rejected on service %u:%u for nodeID %08x - Invalid key!",
               aMajor, aMinor, aNodeID);

      }

      // Print status
      if (sad.maxaccesscount >= 0) {
        dPrint(6,"Currently the service %u:%u has %u authenticated nodes out of maximum %d.",
               aMajor, aMinor, sad.GetAccessCount(), sad.maxaccesscount);
      } else {
        dPrint(6,"Currently the service %u:%u has %u authenticated nodes (maximum unlimited)",
               aMajor, aMinor, sad.GetAccessCount());
      }

    } else {
      // No ServiceAuthentication?
      dPrint(6,"No ServiceAuthentication set; no need for service registration.");

      // Set 'OK' state.
      result = true;
    }
  }

  return result;
}
//*****************************************************************************

void CMaCICtrlServer::CommandSetAuthentication(const CMaCICtrlData &aInputData,
                                               const gimi::GIMIMessage &aMsg)
{

  const TAuthentication *a = aInputData.GetAuthentication();
  const TService *s = aInputData.GetService(0);

  // We need Authentication AND Service in the command packet to
  // attempt to set Authentication.
  if (a && s) {
    // Got a & s.
    bool r = SetClientAuthentication(s->servicemajor, s->serviceminor,
                                     aMsg.getSenderId(),
                                     *a);
    if (r) {
      dPrint(6,"Client Authenticated OK.");

    } else {
      dPrint(6,"Client failed to Authenticate. (Or no free slots left)");

    }
  }
}
//*****************************************************************************

int CMaCICtrlServer::ProcessMessage(const gimi::GIMIMessage &msg)
{
  int result = 0;
  gim::binbag::CBinBag bb;
  CMaCICtrlData d;
  bool r;

  r = bb.Decode(msg.getData(), msg.getDatalength());
  if (r) {
    // Succesfully decoded as BinBag.
    r = d.DecodeFrom(&bb);
    if (r) {
      // Succesfully decoded as MaCICtrl
      // Now, Check whether it was Command, if so, process it and construct reply
      bool send_reply = false;
      gim::binbag::CBinBag replybb;
      CMaCICtrlData reply;
      reply.EncodeTo(&replybb);

      // Pointer reassignment
      const gim::binbag::CBinBag *replydata = &replybb;


      const TCommand *cmd = d.GetCommand();
      if (cmd) {
        switch(cmd->cmd) {
        case KCommandGetInformation: {
          dPrint(8,"Got 'GetInformation' Command");
          CommandGetInformation(reply, d, msg);
          send_reply = true;
          break;
        }

        case KCommandSetAuthentication: {
          dPrint(8,"Got 'SetAuthentication' Command");
          CommandSetAuthentication(d, msg);
          // NOTE: Break is omitted on purpose; we want to process 'GetAccess'
          // After 'SetAuthentication'
          nobreak;
        }

        case KCommandGetAccess: {
          dPrint(8,"Got 'GetAccess' Command");
          CommandGetAccess(reply, d, msg);
          send_reply = true;
          break;
        }

        case KCommandTellAccess: {
          dPrint(8,"Got 'TellAccess' Command from node 0x%08X. Server doesn't process this message. Ignore!",
                 msg.getSenderId());
          break;
        }

        case KCommandKill: {
          // Little easter egg :) Not really, this is a last-ditch
          // protection for a crashed module.
          dPrint(ODTEST,"KILL message received - must die now !");
#ifndef WINDOWS_OS
          raise(SIGKILL);
#else
		  exit(1);
#endif
          break;
        }

        default:
          dPrint(3,"Unidentified command! Received '%d' from node 0x%08X! Ignore.",
                 cmd->cmd, msg.getSenderId());
          break;
        }
      }

      // Check whether reply needs to be sent
      if (send_reply) {

	int gr = iGIMIPtr->send(msg.getSenderId(),
				replydata->GetBuffer(),
				replydata->GetBufferSize(),
				msg.majorTypeId,
				msg.messageId, // This is not a Typo :)
				msg.messageId); // Reply with same ID
	if (gr == GIMI_OK) {
	  dPrint(10,"Succesfully sent message with %d BinBag elements to '%p'",
		 replydata->GetElementCount(),
		 msg.getSenderId());

	} else {

	  dPrint(3,"Failed to send message with %d BinBag elements to '%p'",
		 replydata->GetElementCount(),
		 msg.getSenderId());
	}
      }
    }
  }

  return result;
}
//*****************************************************************************

int CMaCICtrlServer::ThreadFunction(const int aThreadNumber)
{
  int r, result = 0;
  gimi::GIMIMessage gmsg;

  assert(aThreadNumber == 0);
  assert(iGIMIPtr != NULL);

  dPrint(6, "GIMIMessage handler started");

  // Run as long as the thread should be active
  while(iServerActive) {
    // Call the ReceiveMessage wrapper with 5s timeout

    r = iGIMIPtr->receive(gmsg,
                          5000,
                          iInterfaceMajor,
                          iInterfaceMinor,
                          iRedirectionBufferId);
    switch(r) {
    case GIMI_OK:
      // Got GIMI OK, so process message.
      dPrint(15,"Got GIMIMessage %d - Processing.", gmsg.messageId);
      ProcessMessage(gmsg);
      break;

    case GIMI_TIMEOUT:
      dPrint(10,"GIMI Timeout. No messages for %d:%d - %d",
             iInterfaceMajor, iInterfaceMinor, iRedirectionBufferId);
      break;

    case GIMI_INTERRUPT:
    case GIMI_ERROR:
    default:
      // Any error, print warning and sleep for one second.
      dPrint(6,"GIMI Error (%d, '%s') received. Sleeping one second.",
             r, gimi::getGimiErrorString(r).c_str());
      ownSleep_ms(1000);
      break;
    }
  }

  dPrint(6, "GIMIMessage handler stopped");

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
